#include "Dictionary.h"
#include "Mylogger.h"
#include <fstream>
#include <sstream>

using std::ofstream;
using std::ifstream;
using std::istringstream;

bool operator<(const CandidateResult& lhs,const CandidateResult& rhs){
    if(lhs._dist > rhs._dist){
        return true;
    }else if(lhs._dist < rhs._dist){
        return false;
    }else{
        if(lhs._freq > rhs._freq){
            return true;
        }else
            return false;
    }
}
//-----------------------------一些成员函数用到的中间函数-------------------------

size_t nBytesCode(const char ch){
    if(ch & (1<<7)){
        int nBytes = 1;
        for(int idx = 0;idx != 6; ++idx){
            if(ch & (1<<(6-idx))){
                ++nBytes;
            }else{
                break;
            }
        }
        return nBytes;
    }
    return 1;
}

//计算单词的字母数或词语中的汉字数
size_t length(const string& str){
    size_t ilen = 0;
    for(size_t idx=0;idx != str.size(); ++idx){
        int nBytes = nBytesCode(str[idx]);
        idx += (nBytes - 1);
        ++ilen;
    }
    return ilen;
}

//求三个数中的最小的那一个
int triple_min(const int& a,const int& b,const int& c){
    return a < b ? (a < c ? a : c):(b < c ? b : c);
}

//--------------------------------------------------------------------------------
//读取磁盘中的字典和索引信息
void Dictionary::readInfo(){
    //读取词典
    //1.打开文件
    /* cout << "dictPathname = " << _dictPathname << endl; */
    /* cout << "indexPathname = " << _indexPathname << endl; */
    ifstream ifs1(_dictPathname);
    if(!ifs1.good()){
        string info = "open" + _dictPathname +"failed!";
        LogWarn(info);
        return ;
    }
    //2.循环读取文件的每一行
    string line;
    while(getline(ifs1,line)){
        istringstream iss(line);
        /* cout << "line = " << line << "\n"; */
        int idx; 
        string word;
        int freq;
        iss >> idx >> word >> freq;
        /* cout << "idx = " << idx <<"\n"; */
        /* cout << "word = " << word <<"\n"; */
        /* cout << "freq = " << freq <<"\n"; */
        //加入_dict
        _dict.push_back(std::make_pair(word,freq));
    }
    //3.关闭文件
    ifs1.close();

    //读取字典索引
    //1.打开文件
    ifstream ifs2(_indexPathname);
    if(!ifs2.good()){
        string info = "open" + _indexPathname +"failed!";
        LogWarn(info);
        return ;
    }
    //2.循环读取文件的每一行
    string line2;
    while(getline(ifs2,line2)){
        istringstream iss(line2);
        string word;
        int idx;
        set<int> pos;

        iss >> word;
        while(iss >> idx){
            pos.insert(idx);
        }
        //加入_index
        _index[word] = pos;
    }
    //3.关闭文件
    ifs2.close();
}

//根据要查询的单词(词语),返回包括该单词某一个字母的单词(汉字)的并集
void Dictionary::getQuerySet(const string& word,set<int>& unionSet){
    //1.对单词或词语拆分 map<string,set<int>>; string表示字,set表示出现的该字的单词的集合
    map<string,set<int>> recall;
    //取出字母或者汉字
    for(int idx=0;idx<(int)word.size();){
        int nBytes = nBytesCode(word[idx]);
        string character = word.substr(idx,nBytes);
        idx += nBytes;
        //将字加入map
        recall[character] = set<int>();
    }
    //2.得到存在该字的集合
    for(auto & elem : recall){
        string word = elem.first;
        auto it = _index.find(word);
        if(it != _index.end()){
            //在字典索引中找到了改字
            elem.second = it->second;
        }
    }
    //3.将得到的集合进行合并
    for(auto &elem :recall){
        for(int idx: elem.second){
            unionSet.insert(idx);
        }
    }
}

//计算两个单词的最小编辑距离
int Dictionary::editDistance(const string& lhs,const string& rhs){
    //计算最小编辑距离-包括处理中英文
    size_t lhs_len = length(lhs);
    size_t rhs_len = length(rhs);
    int editDist[lhs_len+1][rhs_len+1];
    for(size_t idx = 0; idx <= lhs_len; ++idx){
        editDist[idx][0] = idx;
    }
    for(size_t idx = 0; idx <= rhs_len; ++idx){
        editDist[0][idx] = idx;
    }

    string sublhs,subrhs;
    for(size_t dist_i = 1,lhs_idx = 0; dist_i <= lhs_len;++dist_i,++lhs_idx){
        size_t nBytes = nBytesCode(lhs[lhs_idx]);
        sublhs = lhs.substr(lhs_idx,nBytes);
        lhs_idx += (nBytes - 1);

        for(size_t dist_j = 1,rhs_idx = 0;
            dist_j <= rhs_len; ++dist_j,++rhs_idx)
        {
            nBytes = nBytesCode(rhs[rhs_idx]);
            subrhs = rhs.substr(rhs_idx,nBytes);
            rhs_idx += (nBytes - 1);
            if(sublhs == subrhs){
                editDist[dist_i][dist_j] = editDist[dist_i-1][dist_j-1];
            }else{
                editDist[dist_i][dist_j] =
                    triple_min(editDist[dist_i][dist_j -1]+1,
                               editDist[dist_i-1][dist_j]+1,
                               editDist[dist_i-1][dist_j-1]+1);
            }

        }
    }
    return editDist[lhs_len][rhs_len];
}

//根据得到的并集,构建CandidateResult集合
void Dictionary::getCandidateResult(const string& word,set<int> unionSet,vector<CandidateResult>& ResultList){
    //通过unionSet得到gword(词)和freq(出现频率)
    for(const int& idx:unionSet){
        string gword = _dict[idx].first;
        int freq = _dict[idx].second;
        //计算该单词与关键词的最小编辑距离
        int editDist = editDistance(word,gword);

        //构建CandidateResult对象
        CandidateResult res;
        res._word = gword;
        res._freq = freq;
        res._dist = editDist;
        //将结果加入resultList
        ResultList.push_back(res);
    }
}

//获得关键字未排序的查询结果
void Dictionary::doQuery(const string& word,vector<CandidateResult>& ResultList){
    //根据要查询的单词(词语),返回包括该单词某一个字母的单词(汉字)的并集
    set<int> unionSet;
    getQuerySet(word,unionSet);
    //根据得到的并集,构建CandidateResult集合
    getCandidateResult(word,unionSet,ResultList);
}
